<!DOCTYPE html>
<html lang="en">

<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Nami 客户端</title>
    <style>
        * {
            padding: 0;
            margin: 0;
            box-sizing: border-box;
        }

        body {
            font-family: Arial, sans-serif;
            background-color: #f4f4f9;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
        }

        .container {
            width: 800px;
            background-color: #fff;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
        }

        .form-group {
            margin-bottom: 15px;
        }

        .form-group label {
            display: block;
            margin-bottom: 5px;
            font-weight: bold;
        }

        .form-group input,
        .form-group select,
        .form-group textarea {
            width: 100%;
            padding: 8px;
            border: 1px solid #ccc;
            border-radius: 4px;
            font-size: 14px;
        }

        .form-group textarea {
            resize: none;
            height: 100px;
        }

        .button-group {
            display: flex;
            gap: 10px;
            margin-top: 20px;
        }

        .button-group input[type="button"] {
            padding: 10px 20px;
            background-color: #007bff;
            color: #fff;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 14px;
        }

        .button-group input[type="button"]:hover {
            background-color: #0056b3;
        }

        .output {
            margin-top: 20px;
        }

        .output textarea {
            width: 100%;
            height: 150px;
            padding: 8px;
            border: 1px solid #ccc;
            border-radius: 4px;
            font-size: 14px;
        }

        .reconnect {
            margin-top: 10px;
            padding: 10px 20px;
            background-color: #28a745;
            color: #fff;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 14px;
        }

        .reconnect:hover {
            background-color: #218838;
        }
    </style>
</head>

<body>
<div class="container">
    <form onsubmit="return false">
        <div class="form-group">
            <label for="wsUrl">WebSocket 地址</label>
            <input type="text" id="wsUrl" name="wsUrl" value="ws://localhost:30000/ws" style="width: 100%;">
        </div>
        <div class="form-group">
            <label for="userId">用户 ID</label>
            <input type="text" id="userId" name="userId" placeholder="输入用户 userId">
        </div>
        <div class="form-group">
            <label for="appKey">App Key</label>
            <input type="text" id="appKey" name="appKey" placeholder="输入 appKey">
        </div>
        <div class="form-group">
            <label for="systemOs">系统 OS</label>
            <input type="text" id="systemOs" name="systemOs" placeholder="输入 systemOs">
        </div>
        <div class="button-group">
            <input type="button" value="登录" onclick="login(this.form.userId.value, this.form.systemOs.value, this.form.appKey.value);">
        </div>
        <div class="form-group">
            <label for="cmd">消息类型</label>
            <select id="cmd" name="cmd">
                <option value="3">单聊</option>
                <option value="5">群聊</option>
            </select>
        </div>
        <div class="form-group">
            <label for="receiveId">接收者 ID</label>
            <input type="text" id="receiveId" name="receiveId" placeholder="输入接收者 ID">
        </div>
        <div class="form-group">
            <label for="message">消息内容</label>
            <textarea id="message" name="message" placeholder="输入要发送的内容"></textarea>
        </div>
        <div class="button-group">
            <input type="button" value="发送消息" onclick="sendMsg(this.form.userId.value, this.form.receiveId.value, this.form.cmd.value, this.form.message.value);">
            <input type="button" value="清空输出" onclick="document.getElementById('responseText').value = ''">
        </div>
        <div class="output">
            <label for="responseText">服务器输出：</label>
            <textarea id="responseText" readonly></textarea>
        </div>
        <button class="reconnect" onclick="reconnectSocket()">重新连接</button>
    </form>
</div>

<script type="text/javascript">
    var qosQueue = []; // 存储需要确认的消息 ID
    var isQosRunning = false; // 标记是否正在发送批量 QoS
    function sendBatchQos() {
        if (isQosRunning || qosQueue.length === 0) {
            return;
        }
        isQosRunning = true;

        // 按会话类型分组
        let groupedQos = {};
        qosQueue.forEach(item => {
            if (!groupedQos[item.converType]) {
                groupedQos[item.converType] = [];
            }
            groupedQos[item.converType].push(item.msgId);
        });

        // 发送每组 QoS
        Object.keys(groupedQos).forEach(converType => {
            let msgIds = groupedQos[converType];
            let userId = document.getElementById('userId').value;

            let qosData = {
                senderId: userId,
                receiverId: "0",
                msgIds: msgIds,
                converType: converType
            };

            console.log("发送批量 QoS:", qosData);
            socket.send(createCustomMessage(199, 1, 1, 10, JSON.stringify(qosData)));
        });

        // 清空队列
        qosQueue = [];
        isQosRunning = false;
    }



    var socket;

    // 定义 createCustomMessage 函数
    function createCustomMessage(cmd, version, encrypt, serial, dataStr) {
        // 魔术值固定为8
        const magic = 8;

        // 假设所有字节数据都在0-255之间

        // 创建魔术值视图
        const magicView = new DataView(new ArrayBuffer(1));
        magicView.setUint8(0, magic);

        // 创建cmd视图，现在它是4个字节
        const cmdView = new DataView(new ArrayBuffer(4));
        cmdView.setUint32(0, cmd, false); // 注意endianness（这里是大端序）

        const versionView = new DataView(new ArrayBuffer(1));
        versionView.setUint8(0, version);

        const encryptView = new DataView(new ArrayBuffer(1));
        encryptView.setUint8(0, encrypt);

        const serialView = new DataView(new ArrayBuffer(1));
        serialView.setUint8(0, serial);

        // 将字符串转换为UTF-8编码的ArrayBuffer
        let encoder = new TextEncoder();
        let dataView = encoder.encode(dataStr);

        // 计算并添加4字节的长度字段
        let dataLength = dataView.byteLength;
        console.log("datalenght=", dataLength)
        const lengthView = new DataView(new ArrayBuffer(4));
        lengthView.setUint32(0, dataLength, false); // 注意endianness（这里是大端序）

        // 合并所有的ArrayBuffer
        let totalLength = 1 + 4 + 1 + 1 + 1 + 4 + dataView.byteLength;
        let combinedBuffer = new ArrayBuffer(totalLength);
        let combinedView = new DataView(combinedBuffer);

        // 复制每个部分到combinedBuffer中
        let offset = 0;

        // 首先复制魔术值
        combinedView.setUint8(offset++, magicView.getUint8(0));

        // 然后复制cmd字段
        for (let i = 0; i < 4; i++) {
            combinedView.setUint8(offset++, cmdView.getUint8(i));
        }

        combinedView.setUint8(offset++, versionView.getUint8(0));
        combinedView.setUint8(offset++, encryptView.getUint8(0));
        combinedView.setUint8(offset++, serialView.getUint8(0));

        // 复制长度字段
        for (let i = 0; i < 4; i++) {
            combinedView.setUint8(offset++, lengthView.getUint8(i));
        }

        // 最后复制数据
        for (let i = 0; i < dataView.byteLength; i++) {
            combinedView.setUint8(offset++, dataView[i]);
        }

        return combinedBuffer;
    }

    // 定义 parseBinaryDataToObject 函数
    function parseBinaryDataToObject(event) {
        let combinedView = new DataView(event.data);
        console.log('[receive] total packet length:', combinedView.byteLength);
        // 读取字节数据
        let magic = combinedView.getUint8(0)
        let cmd = combinedView.getUint32(1);

        let version = combinedView.getUint8(5);

        let encrypt = combinedView.getUint8(6);

        let serial = combinedView.getUint8(7);

        let length = combinedView.getUint32(8);

        // 获取字符串数据的位置和长度

        let dataOffset = 12;

        let dataLength = combinedView.byteLength - dataOffset;

        // 创建一个只包含字符串部分的新ArrayBuffer

        let dataBuffer = event.data.slice(dataOffset, dataOffset + dataLength);

        // 解码为字符串
        let decoder = new TextDecoder();

        let dataStr = decoder.decode(dataBuffer);

        //
        // console.log('Received message:');
        //
        // console.log('cmd:', cmd);
        //
        // console.log('version:', version);
        //
        // console.log('encrypt:', encrypt);
        //
        // console.log('serial:', serial);
        //
        // console.log('data:', dataStr);

        return {
            cmd,
            version,
            encrypt,
            serial,
            length,
            data: dataStr
        }

    }

    // 定义 login 函数
    function login(userId, systemOs, appKey) {
        if (!window.WebSocket) {
            return;
        }
        console.log(userId);
        //当websocket状态打开
        if (socket.readyState == WebSocket.OPEN) {

            var cmd = 1;
            var version = 1;
            var serial = 10;
            var encrypt = 1;

            var data = {
                "userId": userId,
                "appKey": appKey,
                "systemOs": systemOs,
                "deviceType": 13,
                "token": null,
                "currTime": new Date().getTime()
            };
            var jsonData = JSON.stringify(data);
            console.log(jsonData);
            socket.send(createCustomMessage(cmd, version, encrypt, serial, jsonData));
        } else {
            alert("连接没有开启");
        }
    }

    // 定义 viewsocket 函数
    function viewsocket() {
        var wsUrl = document.getElementById("wsUrl").value;
        if (window.WebSocket) {
            socket = new WebSocket(wsUrl);

            socket.onmessage = function (event) {
                const receivedObject = parseBinaryDataToObject(event);
                console.log("接收服务器发送过来新消息 ：" + JSON.stringify(receivedObject));

                var ta = document.getElementById("responseText");

                let cmd = receivedObject.cmd;
                let version = receivedObject.version;
                let encrypt = receivedObject.encrypt;
                let serial = receivedObject.serial;
                let bodyLen = receivedObject.length;
                let msgBody = receivedObject.data;

                console.log("当前指令：" + cmd)
                console.log("收到服务端发来的消息: " + msgBody);

                if (cmd == 1003) {  //服务器发来的单聊响应消息
                    console.log("进入单聊响应逻辑.")
                    var bodyJson = JSON.parse(msgBody);
                    console.log("------------------------", bodyJson.data);
                    let databody = bodyJson.data;
                    let messageJson = JSON.parse(databody)
                    console.log("senderId=", messageJson.senderId)
                    let userId = document.getElementById('userId').value;

                    console.log("userId=", userId)
                    if (messageJson.senderId == userId) {
                        ta.value = ta.value + "\n" + "自己：" + messageJson.payload;
                    } else if (messageJson.senderId != userId) {
                        ta.value = ta.value + "\n" + messageJson.senderId + "：" + messageJson.payload;
                    }

                    // 发送者
                    if (userId == messageJson.senderId) {
                        var recvData = {
                            "senderId": userId,
                            "receiverId": "0",
                            "id": messageJson.id,
                            "clientMsgId": messageJson.clientMsgId,
                            "conversationType": messageJson.conversationType
                        }

                        var qosStr = JSON.stringify(recvData);

                        // qos。。 我发送的消息，收到服务器接收成功消息，1.停止发送消息qos, 2.发送ack qos包
                        // socket.send(createCustomMessage(199, 1, 1, 10, qosStr)); // qos 机制
                        // console.log("发送qos完毕.")
                    }
                    // 接收者
                    if (userId != messageJson.senderId) {
                        // // qos
                        qosQueue.push({
                            msgId: messageJson.id,
                            converType: messageJson.conversationType
                        });
                        console.log("单聊消息添加到 QoS 队列:", messageJson.id);

                        // var qosJson = {
                        //     "senderId": userId,
                        //     "receiverId": "0",
                        //     "msgIds": [messageJson.id],
                        //     "converType": messageJson.conversationType
                        // }
                        //
                        // var recvData = JSON.stringify(qosJson);
                        // socket.send(createCustomMessage(199, 1, 1, 10, recvData));
                        // console.log("单聊 发送接收者qos json=", qosJson);

                        var cid = messageJson.cid;

                        var readMsgId = messageJson.id;
                        var readData = {
                            "senderId": messageJson.senderId,
                            "receiverId": messageJson.receiverId,
                            "payload": "消息已读",
                            "payloadType": "text",
                            "type": 105,
                            "id": readMsgId,
                            "cid": cid,
                            "conversationType": messageJson.conversationType
                        }
                        console.log("发送用户已读", readData);

                        var jsonData = JSON.stringify(readData);

                        socket.send(createCustomMessage(4, 1, 1, 10, jsonData));
                    }

                } else if (cmd == 1005) {  // 群聊
                    // 发送者
                    var bodyJson = JSON.parse(msgBody);
                    console.log("群聊处理：", bodyJson.data);
                    let databody = bodyJson.data;
                    let messageJson = JSON.parse(databody)

                    let userId = document.getElementById('userId').value;

                    if (messageJson.senderId == userId) {
                        ta.value = ta.value + "\n" + "自己：" + messageJson.payload;
                    } else if (messageJson.senderId != userId) {
                        ta.value = ta.value + "\n" + messageJson.senderId + "：" + messageJson.payload;
                    }


                    if (userId == messageJson.senderId) {


                    } else {
                        // qos
                        qosQueue.push({
                            msgId: messageJson.id,
                            converType: messageJson.conversationType
                        });
                        console.log("群聊消息添加到 QoS 队列:", messageJson.id);
                        // var qosJson = {
                        //     "senderId": userId,
                        //     "receiverId": "0",
                        //     "msgIds": [messageJson.id],
                        //     "converType": messageJson.conversationType
                        // }
                        //
                        // var recvData = JSON.stringify(qosJson);
                        // socket.send(createCustomMessage(199, 1, 1, 10, recvData));
                        // console.log("群消息，发送接收者qos json=", recvData);

                        // 已读


                    }


                } else if (cmd == 1001) { //登录响应
                    var resp = JSON.parse(msgBody);
                    if (resp.code == "200") {
                        ta.value = "登录成功";
                        heartBeat();
                    } else if (resp.code == "1002") {
                        ta.value = "已在线不能重复登录";
                    }
                } else if (cmd == 1002) { // pong
                    console.log("[pong] 收到服务器返回pong.")
                    heartBeat();
                    // ta.value = 'pong:' + JSON.stringify(receivedObject)
                } else if (cmd == 54) {
                    var msg = eval("(" + msgBody + ")");
                    console.log("msg 数据: " + msg)

                    ta.value = "系统重启，重连其他机器";
                } else if (cmd == 9999) {
                    var msg = eval("(" + msgBody + ")");
                    console.log(msg)
                    console.log(userId)

                    if (msg["userId"] == "system") {
                        ta.value = ta.value + "\n 系统：" + msg.data;
                    } else if (msg["userId"] == userId) {
                        let msgInfo = msg.data;
                        msgInfo = eval("(" + msgInfo + ")");
                        ta.value = ta.value + "\n" + "自己：" + msgInfo.msgBody;
                    } else {
                        let msgInfo = msg.data;
                        msgInfo = eval("(" + msgInfo + ")");
                        ta.value = ta.value + "\n" + msg.to + "：" + msgInfo.msgBody;
                    }
                }
            }

            //连接建立的回调函数
            socket.onopen = function (event) {
                socket.binaryType = "arraybuffer";
                var ta = document.getElementById("responseText");
                ta.value = "连接开启";
            }

            //连接断掉的回调函数
            socket.onclose = function (event) {
                var ta = document.getElementById("responseText");
                ta.value = ta.value + "\n" + "连接关闭";
            }
        } else {
            alert("浏览器不支持 WebSocket！");
        }

        // 启动定时任务，每 5 秒发送一次批量 QoS
        setInterval(sendBatchQos, 1000);
    }

    // 定义 reconnectSocket 函数
    function reconnectSocket() {
        if (socket) {
            socket.close(); // 关闭现有连接
        }
        viewsocket(); // 重新连接
    }

    // 定义 heartBeat 函数
    function heartBeat() {
        setTimeout(() => {
            console.log("ping...")
            var messageData = {
                // "sendId": ""
            }
            var jsonData = JSON.stringify(messageData);
            console.log(jsonData)
            socket.send(createCustomMessage(2, 1, 1, 10, jsonData));
        }, 55000);
    }

    // 定义 sendMsg 函数
    function sendMsg(userId, receiveId, cmd, msg) {
        if (!window.WebSocket) {
            return;
        }
        console.log(cmd)
        //当websocket状态打开
        if (socket.readyState == WebSocket.OPEN) {
            // debugger;

            var version = 1;
            var encrypt = 1;
            var serial = 10;

            var messageType = 1;

            // 4 群聊
            if (cmd == 5) {
                messageType = 2;
            }

            if (cmd == null || cmd == '') {
                cmd = 3;
            }

            var messageId = uuid();

            var messageData = {};
            if (cmd == 3) {
                var payload = "{\"text\":\"" + msg + "\"}";
                messageData = {
                    "clientMsgId": messageId,
                    "senderId": userId,
                    "receiverId": receiveId,
                    "flow": "up",
                    "type": messageType,
                    "payload": payload,
                    "payloadType": "text"
                }
            } else if (cmd == 5) {
                var payload = "{\"text\":\"" + msg + "\"}";
                messageData = {
                    "clientMsgId": messageId,
                    "senderId": userId,
                    "receiverId": receiveId,
                    "flow": "up",  //下行消息
                    "type": messageType,
                    "payload": payload,
                    "payloadType": "text"
                }
            } else {
                messageData = JSON.parse(msg)
            }
            // data.data = messageData;

            var jsonData = JSON.stringify(messageData);
            console.log(jsonData)
            socket.send(createCustomMessage(cmd, version, encrypt, serial, jsonData));
        } else {
            alert("连接没有开启");
        }
    }

    // 定义 uuid 函数
    function uuid() {
        var s = [];
        var hexDigits = "0123456789abcdef";
        for (var i = 0; i < 36; i++) {
            s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
        }
        s[14] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
        s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
        s[8] = s[13] = s[18] = s[23] = "-";

        var uuid = s.join("");
        return uuid;
    }

    // 初始化 WebSocket 连接
    viewsocket();
</script>
</body>

</html>
